using System;
using System.Collections.Generic;
using System.Diagnostics;
using FwkLight.CodeExtensions;
using FwkLight.Domain.Fetching;
using FwkLight.Domain.Repository;
using FwkLight.Persistence;
using log4net;
using NHibernate;

namespace FwkLight.Application
{
  public interface IBaseSaveTask<EntityType> : IApplicationTask<EntityType>, IOrderedSaver
  {
    void StartOrContinueLongConversation ( );
    void EndLongConversation ( );
    bool IsInLongConversation ( );
    bool HasInitiatedLongConversation ( );
    void ForFetchingUse ( IFetchingStrategy<EntityType> fetchingStrategy );
  }

  public abstract class BaseSaveTask<EntityType> : BaseLoadTask<EntityType>, IBaseSaveTask<EntityType>
  {
    private readonly IList<IOrderedSaver> _saversInRightOrder;
    private bool _waitForSaveCall;
    private ILog _logger = LogManager.GetLogger(typeof(BaseSaveTask<EntityType>));

    private IOrderedSaver _currentOrderedSaver;

    [DebuggerNonUserCode]
    protected BaseSaveTask ( IBaseRepo<EntityType> repository, INHUnitOfWorkProvider uowProvider )
      : base(repository, uowProvider)
    {
      _saversInRightOrder = new List<IOrderedSaver> { this };
    }

    public ApplicationTaskTransaction NewTransaction
    {
      [DebuggerNonUserCodeAttribute]
      get { return new ApplicationTaskTransaction(UOW); }
    }

    [DebuggerNonUserCodeAttribute]
    internal override void OnAfterExecuteLogicAndChecks ( )
    {
      if (!_waitForSaveCall)
        SaveFinalData();
    }

    protected override void ExecuteInternal ( )
    {
      ConfigureSaveOrder();
      base.ExecuteInternal();
    }

    [DebuggerNonUserCodeAttribute]
    protected virtual void ConfigureSaveOrder ( )
    {

    }

    [DebuggerNonUserCodeAttribute]
    protected virtual void OnAfterSave ( )
    {

    }

    [DebuggerNonUserCodeAttribute]
    public void ShouldSaveAfterThis ( )
    {
      _saversInRightOrder.Add(_currentOrderedSaver);
      _currentOrderedSaver.WaitForSaveCall();
      OnAfterConfiguredForOrderedSave();
    }

    [DebuggerNonUserCodeAttribute]
    public IOrderedSaver Task ( IOrderedSaver orderedSaver )
    {
      _currentOrderedSaver = orderedSaver;
      return this;
    }

    [DebuggerNonUserCodeAttribute]
    public void ShouldSaveBeforeThis ( )
    {
      _saversInRightOrder.Insert(0, _currentOrderedSaver);
      _currentOrderedSaver.WaitForSaveCall();
      OnAfterConfiguredForOrderedSave();
    }

    [DebuggerNonUserCodeAttribute]
    protected virtual void OnAfterConfiguredForOrderedSave()
    {
      
    }

    [DebuggerNonUserCodeAttribute]
    public void WaitForSaveCall ( )
    {
      _waitForSaveCall = true;
    }

    public virtual void SaveFinalData ( )
    {
      if (!TaskRequirementsAreSatisfied) return;

      if (IsInLongConversation() && !LongConversationFinishReached())
        return;

      if (!UOW.TransactionInProgress)
      {
        using (NewTransaction)
        {
          SaveTasksDataInOrder();
        }
      }
      else SaveTasksDataInOrder();

      OnAfterSave();

      if (IsInLongConversation())
        EndLongConversation();
    }

    [DebuggerNonUserCodeAttribute]
    protected virtual void SaveTasksDataInOrder ( )
    {
      _saversInRightOrder.ForEach(p =>
                                   {
                                     if (p == this)
                                     {
                                       SaveAggregateInTransaction();
                                       SaveAdditionalDataInTransaction();
                                     }
                                     else
                                       p.SaveFinalData();
                                   });
    }

    [DebuggerNonUserCodeAttribute]
    protected virtual void SaveAggregateInTransaction ( )
    {
      if (Entity != null)
        Repository.SaveOrUpdate(Entity);      
    }

    [DebuggerNonUserCodeAttribute]
    protected virtual void SaveAdditionalDataInTransaction(){}

    [DebuggerNonUserCodeAttribute]
    public void StartOrContinueLongConversation ( )
    {
      if (IsInLongConversation())
        return;

      SessionItemDictionary.Add(PersistenceKeys.CurrentlyRunningLongTransaction, true);
      SessionItemDictionary.Add(PersistenceKeys.LongTransactionInitiator, GetType().Name);
      SessionItemDictionary.Add(PersistenceKeys.LongTransactionUnitOfWork, UOW);
    }

    [DebuggerNonUserCodeAttribute]
    public void EndLongConversation ( )
    {
      SessionItemDictionary.Remove(PersistenceKeys.CurrentlyRunningLongTransaction);
      SessionItemDictionary.Remove(PersistenceKeys.LongTransactionInitiator);
      SessionItemDictionary.Remove(PersistenceKeys.LongTransactionUnitOfWork);
    }

    [DebuggerNonUserCodeAttribute]
    public bool IsInLongConversation ( )
    {
      return SessionItemDictionary.Contains(PersistenceKeys.CurrentlyRunningLongTransaction)
             && SessionItemDictionary.Get(PersistenceKeys.CurrentlyRunningLongTransaction);
    }

    [DebuggerNonUserCodeAttribute]
    public bool HasInitiatedLongConversation ( )
    {
      return SessionItemDictionary.Contains(PersistenceKeys.LongTransactionInitiator)
             && SessionItemDictionary.Get(PersistenceKeys.LongTransactionInitiator) == GetType().Name;
    }

    [DebuggerNonUserCodeAttribute]
    protected virtual bool LongConversationFinishReached ( )
    {
      return HasInitiatedLongConversation();
    }

    [DebuggerNonUserCodeAttribute]
    protected override void LoadEntity ( )
    {
      if (Entity == null)
        base.LoadEntity();
    }

    [DebuggerNonUserCodeAttribute]
    public void ShouldSaveInsteadOf ( IOrderedSaver taskWhichShouldNotSave )
    {
      taskWhichShouldNotSave.WaitForSaveCall();
      OnAfterConfiguredForOrderedSave();
    }

    protected void RetryOnVersionConflict ( Action action )
    {
      bool succesfull = false;
      int counter = 0;

      while (!succesfull && counter < 3)
      {
        try
        {
          action();
          succesfull = true;
        }
        catch (StaleObjectStateException ex)
        {
          ApplicationTask.ClearTaskEntitiesFromRequest();
          UOW.Session.Clear();
          counter++;
          _logger.Error("Version Conflict on product selection", ex);
        }
      }
    }
  }
}